/**
 *
 * \file
 * Infrared transmitter.
 *
 */

/*
 *
 * Copyright (c) 2010, Jesper Hansen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *      * Redistributions of source code must retain the above copyright notice, this
 *       list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *     * Neither the name of the copyright holders nor the names of any contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 */



/*! \addtogroup commands Command Protocol

where
	- \b connectoraddress are specified as <b>1:N</b>, where 1 is the address of the IR module and N is
the channel number to use. Valid values are 1 to 3.
	- \b id is an identifier for this command. It will be used in the completeir response later.

*/

/*! \addtogroup ir IR Transmission

<b><em>Note: While most of the text here refers to sending IR and IR commands, this is by no means the only use.
Sending data streams is perhaps more correct. Whether there is an IR diode or a radio module connected
does not matter. Still, IR is the most common use, so that's where the main focus will be kept.</em></b>\n
\n
\n

\par Overview

The IR module allows data streams to be sent, controlling an IR diode, which in turn can control a TV, video
or similar.\n
These data streams are similar to the data streams sent from a regular TV remote.\n
\n
The IR module contain three separate channels, each controlled by a PWM generator. The PWM generators allow
a large variety of carrier frequencies to be specified, and take most of the load off the processor.\n
\n


\par Data Structure

The command details can be found in the command reference section. Here only a short description of
the IR specific commands are listed.\n
\n
The command for sending data on one of the three IR channels are:\n
\n
\b sendir,connectoraddress,id,frequency,repeat,offset,on1,off1,on2,on2,....,onN,offN\n
\n
This causes the specified on/off sequence to be sent on the IR output channel in the following manner:\n

\verbatim
          _   _   _   _   _                   _   _   _   _   _   _
     ____| |_| |_| |_| |_| |_________________| |_| |_| |_| | |_| |_|___________
period   | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12| 13| 13| 14| .......
         |     on1 = 5       |    off1 = 4   |        on2 = 6        |
\endverbatim

The above is the start of a sequence with on/off specified as 5,4,6,......\n
The period time is specified as 1/\b f where \b f is the frequency given in the sendir command. Typical frequencies
are 30-45 kHz, but some systems, like B&O use 400kHz.


\par Output and Input Modes

Four channel modes are currently supported, two output modes and two input modes:\n
\n
	- \b IRCM_OUT	Normal Output Mode.\n
In this mode, data is sent as pulses overlaid on a modulating frequency, as described above in the Data Structure section.\n
\n
	- \b IRCM_OUT_NO_CARRIER	Output Mode without carrier.\n
This mode is similar to the \b IRCM_OUT mode, except there is no carrier. Only the pulses are sent. This is
the mode normally used when interfacing a radio module.\n
\n
 	- \b IRCM_IN	Normal Input Mode\n
In this input mode, data is continuously sampled, and teh status can be requested with the \b getstate command.\n
\n
 	- \b IRCM_IN_NOIFY	Normal Input Mode\n
In this mode, data is continuously sampled, and a \b sensornotify message is sent for each state change.\n
\n

\n

\verbatim
	Internal Enum          Name on config webpage     Description
	-------------------    ----------------------     --------------------------------
	IRCM_OUT               IR Out                     Normal Output mode
	IRCM_OUT_NOCARRIER     IR Out No Carrier          Unmodulated Output mode
	IRCM_IN                IR In                      Normal Input mode
	IRCM_IN_NOTIFY         IR In Notify               Input mode with Notify messages
\endverbatim

bla bla bla


<table>
<tr><th>Internal Enum</th><th>Name on config webpage</th><th>Description</th></tr>
<tr><td>IRCM_OUT</td><td>IR Out</td><td>Normal Output mode</td></tr>
<tr><td>IRCM_OUT_NOCARRIER</td><td>IR Out No Carrier</td><td>Unmodulated Output mode</td></tr>
<tr><td>IRCM_IN</td><td>IR In</td><td>Normal Input mode</td></tr>
<tr><td>IRCM_IN_NOTIFY</td><td>IR In Notify</td><td>Input mode with Notify messages</td></tr>
</table>

*/


/*******************************************************************************************************/


/*! \addtogroup nexa_protocol Nexa Protocol (old)

\par Bit representation

The bit coding used by the encoder chips, for example M3E. from MOSDESIGN SEMICONDUCTOR,  allows
for trinary codes, ie '0','1' and 'X' (OPEN/FLOATING).

However, it seems that only '0' and 'X' is currently used in the  NEXA/PROOVE remotes.

The high level in the ASCII-graphs below denotes the transmission of the 433 MHz carrier.

The low level means no carrier.

\verbatim

'0' bit:
 ____              ____
|    |            |    |
|    |            |    |
|    |____________|    |____________

|<-->|<---------->|<-->|<---------->|
   T       3T        T       3T


'1' bit:
 ____________      ____________
|            |    |            |
|            |    |            |
|            |____|            |____

|<---------->|<-->|<---------->|<-->|
      3T        T       3T        T


'X' (OPEN) bit:
 ____              ____________
|    |            |            |
|    |            |            |
|    |____________|            |____

|<-->|<---------->|<---------->|<-->|
   T       3T            3T       T


'STOP/SYNC' bit (Note! Longer than the data bit):
 ____
|    |
|    |
|    |__________________________...____

|<-->|--------------------------...--->|
   T                32T

\endverbatim

If the time T is (the nominal) 350us, one data bit time is 8 T; 2.8ms.


\par Packet format

Each packet is made up of 12 trinary bits followed by a stop/sync bit.
Packets are sent n*4 times or more.

If we call the 12 data bits b0..b11 where b0 is sent first, they will have the
following meaning:
\verbatim
b0..b3 (4 bits) - House (address)
0000 - A
X000 - B
0X00 - C
XX00 - D
00X0 - E
X0X0 - F
0XX0 - G
XXX0 - H
000X - I
X00X - J
0X0X - K
XX0X - L
00XX - M
X0XX - N
0XXX - O
XXXX - P

b4..b7 - Unit
0000 - 1
X000 - 2
0X00 - 3
XX00 - 4
00X0 - 5
X0X0 - 6
0XX0 - 7
XXX0 - 8
000X - 9
X00X - 10
0X0X - 11
XX0X - 12
00XX - 13
X0XX - 14
0XXX - 15
XXXX - 16

b8..b10 - Unknown
0XX

b11 - Activation
0 - OFF
X - ON
\endverbatim
*/


/**************************************************************************************************/


#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_nvic.h"
#include "inc/hw_types.h"
#include "driverlib/gpio.h"
#include "driverlib/pwm.h"
#include "driverlib/interrupt.h"
#include "driverlib/sysctl.h"
#include "isendir.h"
#include "ir.h"

ir_data_t *pir_data[NUM_IR_CHANNELS];


void GPIOC_IntHandler(void)
{

}


void GPIOD_IntHandler(void)
{

}


int common_pwm_handler(int index)
{
	ir_data_t *pid = pir_data[index];

    if (pid->ir_count)
    	pid->ir_count--;

    if (pid->ir_count == 0)
    {
    	pid->ir_cur_step++;

    	if (pid->ir_cur_step == pid->ir_steps)
    	{
    		pid->ir_cur_repeat++;
    		if (pid->ir_cur_repeat == pid->ir_repeat)
    		{
    			switch (pid->ir_channel)
    			{
    			case 0:
    		    	IntDisable(INT_PWM0);
    		    	break;
    			case 1:
    		    	IntDisable(INT_PWM1);
    		    	break;
    			case 2:
    		    	IntDisable(INT_PWM2);
    		    	break;
    			}
    			pid->ir_busy = 0;	// all done
    			return -1;
    		}
    		pid->ir_cur_step = pid->ir_offset - 1;
    	}

    	pid->ir_count = pid->ir_table[pid->ir_cur_step];

    	return pid->ir_cur_step & 1;
    }
    return -1;
}

//*****************************************************************************
//!
//!  Interrupt handler for the for PWM0 interrupts
//!
//*****************************************************************************
void PWM0_IntHandler(void)
{
    //
    // Clear the PWM1 LOAD interrupt flag.
    //
    PWMGenIntClear(PWM_BASE, PWM_GEN_0, PWM_INT_CNT_LOAD);

    //
    // call common handler and adjust outputs depening on the result
    //
    switch ( common_pwm_handler(0) )
    {
    case 0:
    	if (ir_settings.is_channel_mode[0] == IRCM_OUT_NOCARRIER)
    		GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_0, GPIO_PIN_0);	// set pin
    	else
    		PWMOutputState(PWM_BASE, PWM_OUT_0_BIT, true);	// turn on PWM

    case 1:
    	if (ir_settings.is_channel_mode[0] == IRCM_OUT_NOCARRIER)
    		GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_0, 0);	// clear pin
    	else
    		PWMOutputState(PWM_BASE, PWM_OUT_0_BIT, false);	// turn off PWM

    default:
    	break;
    }
}

//*****************************************************************************
//!
//!  Interrupt handler for the for PWM1 interrupts
//!
//*****************************************************************************
void PWM1_IntHandler(void)
{
    //
    // Clear the PWM1 LOAD interrupt flag.
    //
    PWMGenIntClear(PWM_BASE, PWM_GEN_1, PWM_INT_CNT_LOAD);

    //
    // call common handler and adjust outputs depening on the result
    //
    switch ( common_pwm_handler(1) )
    {
    case 0:
    	if (ir_settings.is_channel_mode[1] == IRCM_OUT_NOCARRIER)
    		GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_0, GPIO_PIN_0);	// set pin
    	else
    		PWMOutputState(PWM_BASE, PWM_OUT_2_BIT, true);	// turn on PWM

    case 1:
    	if (ir_settings.is_channel_mode[1] == IRCM_OUT_NOCARRIER)
    		GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_0, 0);	// clear pin
    	else
    		PWMOutputState(PWM_BASE, PWM_OUT_2_BIT, false);	// turn off PWM

    default:
    	break;
    }
}

//*****************************************************************************
//!
//!  Interrupt handler for the for PWM2 interrupts
//!
//*****************************************************************************
void PWM2_IntHandler(void)
{
    //
    // Clear the PWM1 LOAD interrupt flag.
    //
    PWMGenIntClear(PWM_BASE, PWM_GEN_2, PWM_INT_CNT_LOAD);

    //
    // call common handler and adjust outputs depening on the result
    //
    switch ( common_pwm_handler(2) )
    {
    case 0:
    	if (ir_settings.is_channel_mode[2] == IRCM_OUT_NOCARRIER)
    		GPIOPinWrite(GPIO_PORTE_BASE, GPIO_PIN_0, GPIO_PIN_0);	// set pin
    	else
    		PWMOutputState(PWM_BASE, PWM_OUT_4_BIT, true);	// turn on PWM

    case 1:
    	if (ir_settings.is_channel_mode[2] == IRCM_OUT_NOCARRIER)
    		GPIOPinWrite(GPIO_PORTE_BASE, GPIO_PIN_0, 0);	// clear pin
    	else
    		PWMOutputState(PWM_BASE, PWM_OUT_4_BIT, false);	// turn off PWM

    default:
    	break;
    }
}



//*****************************************************************************
//!
//! Send a stream of data to an IR port
//!
//! \param	ptr_ir_data	Pointer to an ir_data_t structure with timing data
//!
//*****************************************************************************
void ir_send(ir_data_t *ptr_ir_data)
{
	uint32_t f;
	uint16_t channel;

	channel = ptr_ir_data->ir_channel;

	// check the current mode, if not an output mode,
	// we ignore this call

	if ( (ir_settings.is_channel_mode[channel] == IRCM_IN) ||
		 (ir_settings.is_channel_mode[channel] == IRCM_IN_NOTIFY) )
		return;


	// store pointer to this structure locally
	pir_data[channel] = ptr_ir_data;

	//
	// initialize working vars
	//
	ptr_ir_data->ir_busy = 1;
	ptr_ir_data->ir_cur_step = 0;
	ptr_ir_data->ir_cur_repeat = 0;
	ptr_ir_data->ir_count = ptr_ir_data->ir_table[0];

	f = 8000000L;
	f /= ptr_ir_data->ir_frequency;
	if ( f < 10)
		f = 10;
	if (f > 65530)
		f = 65530;

	switch (ptr_ir_data->ir_channel)
	{
	case 0:	// PWM generator 0, PWM0
		PWMGenConfigure(PWM_BASE, PWM_GEN_0, PWM_GEN_MODE_UP_DOWN | PWM_GEN_MODE_NO_SYNC);
    	PWMGenPeriodSet(PWM_BASE, PWM_GEN_0, f );		// set PWM frequency
    	PWMPulseWidthSet(PWM_BASE, PWM_OUT_0, f/2);		// set 50% duty cycle

    	//
    	// if NOCARRIER mode is requested, we setup the pin as a regular I/O pin and
    	// toggle the pin at each PWM interrupt, otherwise we setup PWM normally
    	//
    	if (ir_settings.is_channel_mode[channel] == IRCM_OUT_NOCARRIER)
        	GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_0, GPIO_PIN_0);	// enable I/O pin as output
    	else
        	PWMOutputState(PWM_BASE, PWM_OUT_0_BIT, true);			// let PWM control I/O pin

    	PWMGenEnable(PWM_BASE, PWM_GEN_0);							// enable PWM generator

    	//
    	// setup and enable interrupt for this PWM channel
    	//
    	PWMIntEnable(PWM_BASE, PWM_INT_GEN_0);
    	PWMGenIntTrigEnable(PWM_BASE, PWM_GEN_0, PWM_INT_CNT_LOAD);
    	IntEnable(INT_PWM0);
		break;

	case 1:	// PWM generator 1, PWM2
		PWMGenConfigure(PWM_BASE, PWM_GEN_1, PWM_GEN_MODE_UP_DOWN | PWM_GEN_MODE_NO_SYNC);
    	PWMGenPeriodSet(PWM_BASE, PWM_GEN_1, f );		// set PWM frequency
    	PWMPulseWidthSet(PWM_BASE, PWM_OUT_2, f/2);		// set 50% duty cycle

    	//
    	// if NOCARRIER mode is requested, we setup the pin as a regular I/O pin and
    	// toggle the pin at each PWM interrupt, otherwise we setup PWM normally
    	//
    	if (ir_settings.is_channel_mode[channel] == IRCM_OUT_NOCARRIER)
        	GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_0, GPIO_PIN_0);	// enable I/O pin as output
    	else
        	PWMOutputState(PWM_BASE, PWM_OUT_2_BIT, true);			// let PWM control I/O pin

    	PWMGenEnable(PWM_BASE, PWM_GEN_1);							// enable PWM generator

    	//
    	// setup and enable interrupt for this PWM channel
    	//
    	PWMIntEnable(PWM_BASE, PWM_INT_GEN_1);
    	PWMGenIntTrigEnable(PWM_BASE, PWM_GEN_1, PWM_INT_CNT_LOAD);
    	IntEnable(INT_PWM1);
    	break;

	case 2:	// PWM generator 2, PWM4
		PWMGenConfigure(PWM_BASE, PWM_GEN_2, PWM_GEN_MODE_UP_DOWN | PWM_GEN_MODE_NO_SYNC);
    	PWMGenPeriodSet(PWM_BASE, PWM_GEN_2, f );		// set PWM frequency
    	PWMPulseWidthSet(PWM_BASE, PWM_OUT_4, f/2);		// set 50% duty cycle

    	//
    	// if NOCARRIER mode is requested, we setup the pin as a regular I/O pin and
    	// toggle the pin at each PWM interrupt, otherwise we setup PWM normally
    	//
    	if (ir_settings.is_channel_mode[channel] == IRCM_OUT_NOCARRIER)
        	GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_0, GPIO_PIN_0);	// enable I/O pin as output
    	else
        	PWMOutputState(PWM_BASE, PWM_OUT_4_BIT, true);			// let PWM control I/O pin

    	PWMGenEnable(PWM_BASE, PWM_GEN_2);							// enable PWM generator

    	//
    	// setup and enable interrupt for this PWM channel
    	//
    	PWMIntEnable(PWM_BASE, PWM_INT_GEN_2);
    	PWMGenIntTrigEnable(PWM_BASE, PWM_GEN_2, PWM_INT_CNT_LOAD);
    	IntEnable(INT_PWM2);
		break;
	}
}


void ir_stop(int channel)
{



}




//*****************************************************************************
//!
//! Initialize the IR channels and interrupts
//!
//*****************************************************************************
void ir_init(void)
{
	//
	// this basic setup of clocks are always done
	// and are unrelated to the direction of the IR port
	//
	SysCtlPWMClockSet(SYSCTL_PWMDIV_1);				// set main PWM clock to sysclock / 1
    SysCtlPeripheralEnable(SYSCTL_PERIPH_PWM);		// enable clock to PWM modules
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);	// enable clock to periphals on PORT B
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);	// enable clock to periphals on PORT C
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);	// enable clock to periphals on PORT D
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE);	// enable clock to periphals on PORT E
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);	// enable clock to periphals on PORT F

    //
    // clear old interrupts which may have been enabled
    //
    GPIOPinIntDisable(GPIO_PORTD_BASE, GPIO_PIN_7);
    GPIOPinIntDisable(GPIO_PORTC_BASE, GPIO_PIN_6);
    GPIOPinIntDisable(GPIO_PORTC_BASE, GPIO_PIN_7);


    // channel 0, IR_IN_1, IR_OUT_1, PF0, PWM 0
    if (ir_settings.is_channel_mode[0] == IRCM_OUT)	// normal IR output
    {
    	GPIOPinConfigure(GPIO_PF0_PWM0);
    	GPIOPinTypePWM(GPIO_PORTF_BASE, GPIO_PIN_0);
    }
    else if (ir_settings.is_channel_mode[0] == IRCM_OUT_NOCARRIER)	// Output without carrier frequency
    {
        GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_0);
    	GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_0, 0);
    }
    else
    {
        GPIODirModeSet(GPIO_PORTD_BASE, GPIO_PIN_7, GPIO_DIR_MODE_IN);
        GPIOPadConfigSet(GPIO_PORTD_BASE, GPIO_PIN_7, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);
        if (ir_settings.is_channel_mode[0] == IRCM_IN_NOTIFY)
        {
        	GPIOIntTypeSet(GPIO_PORTD_BASE, GPIO_PIN_7, GPIO_BOTH_EDGES);	// set interrupt type
        	GPIOPinIntClear(GPIO_PORTD_BASE, GPIO_PIN_7);					// clear any pending interrupts
        	GPIOPinIntEnable(GPIO_PORTD_BASE, GPIO_PIN_7);					// enable interrupt for the pin
        	IntEnable(INT_GPIOD);											// enable interrupts for port
        }
    }

    // channel 1, IR_IN_2, IR_OUT_2, PB0, PWM 2
    if (ir_settings.is_channel_mode[0] == IRCM_OUT)	// normal IR output
    {
    	GPIOPinConfigure(GPIO_PB0_PWM2);
    	GPIOPinTypePWM(GPIO_PORTB_BASE, GPIO_PIN_0);
    }
    else if (ir_settings.is_channel_mode[0] == IRCM_OUT_NOCARRIER)	// Output without carrier frequency
    {
        GPIOPinTypeGPIOOutput(GPIO_PORTB_BASE, GPIO_PIN_0);
    	GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_0, 0);
    }
    else
    {
        GPIODirModeSet(GPIO_PORTC_BASE, GPIO_PIN_6, GPIO_DIR_MODE_IN);
        GPIOPadConfigSet(GPIO_PORTC_BASE, GPIO_PIN_6, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);
        if (ir_settings.is_channel_mode[1] == IRCM_IN_NOTIFY)
        {
        	GPIOIntTypeSet(GPIO_PORTC_BASE, GPIO_PIN_6, GPIO_BOTH_EDGES);	// set interrupt type
        	GPIOPinIntClear(GPIO_PORTC_BASE, GPIO_PIN_6);					// clear any pending interrupts
        	GPIOPinIntEnable(GPIO_PORTC_BASE, GPIO_PIN_6);					// enable interrupt for the pin
        	IntEnable(INT_GPIOC);											// enable interrupts for port
        }
    }


    // channel 2, IR_IN_3, IR_OUT_3, PE0, PWM 4
    if (ir_settings.is_channel_mode[0] == IRCM_OUT)	// normal IR output
    {
    	GPIOPinConfigure(GPIO_PE0_PWM4);
    	GPIOPinTypePWM(GPIO_PORTE_BASE, GPIO_PIN_0);
    }
    else if (ir_settings.is_channel_mode[0] == IRCM_OUT_NOCARRIER)	// Output without carrier frequency
    {
        GPIOPinTypeGPIOOutput(GPIO_PORTE_BASE, GPIO_PIN_0);
    	GPIOPinWrite(GPIO_PORTE_BASE, GPIO_PIN_0, 0);
    }
    else
    {
        GPIODirModeSet(GPIO_PORTC_BASE, GPIO_PIN_7, GPIO_DIR_MODE_IN);
        GPIOPadConfigSet(GPIO_PORTC_BASE, GPIO_PIN_7, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);
        if (ir_settings.is_channel_mode[2] == IRCM_IN_NOTIFY)
        {
        	GPIOIntTypeSet(GPIO_PORTC_BASE, GPIO_PIN_7, GPIO_BOTH_EDGES);	// set interrupt type
        	GPIOPinIntClear(GPIO_PORTC_BASE, GPIO_PIN_7);					// clear any pending interrupts
        	GPIOPinIntEnable(GPIO_PORTC_BASE, GPIO_PIN_7);					// enable interrupt for the pin
        	IntEnable(INT_GPIOC);											// enable interrupts for port
        }
    }

}

